#include "pch.hpp"
#include "System.hpp"
#include "String.hpp"








Engine::Script::System::System(LogCallback logCallback)
	: m_logCallback(logCallback)
	, m_engine(asCreateScriptEngine(ANGELSCRIPT_VERSION))
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

Engine::Script::System::System(const EngineInfo& info)
	: m_logger(info.serviceProvider->service<logger::stream>())
	, m_engine(info.angelscript != nullptr ? info.angelscript : asCreateScriptEngine(ANGELSCRIPT_VERSION))
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::post_create(ptr _this)
{
	verify(m_engine != nullptr);

	m_this = _this;

	// We want unicode string encoding
	verify(m_engine->GetEngineProperty(asEP_SCRIPT_SCANNER) == 1);
	// And we want to use UTF-8
	verify(m_engine->GetEngineProperty(asEP_STRING_ENCODING) == 0);

	m_engine->SetMessageCallback(asMETHOD(System, callback), this, asCALL_THISCALL);

	m_contextPool = ContextPool::create(m_engine, _this);
	registerQString(_this);

	if(m_logger)
		m_logger->log_debug(QString("Successfully created Angelscript Engine ver. %1").arg(ANGELSCRIPT_VERSION_STRING));
}
///////////////////////////////////////////////////////////////////////////////////////////////////




QString Engine::Script::System::name() const
{
	return "ScriptSystem";
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::update()
{
	// TODO: Run the gc
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Engine::Script::System::compareState(boostext::iostreams::bin_istream& stream)
{
	// Just in case, update the cache...
	reflect();

	uint16 affe;
	stream >> affe;
	if(affe != 0xAFFE)
		throw invalid_value_exception("Delimiter doesn't match: This doesn't seem to be a state", affe);

	std::streamsize pos;
	stream >> pos;


	//
	// Compare version
	//

	int version;
	stream >> version;
	if(version != ANGELSCRIPT_VERSION)
		throw invalid_value_exception("Angelscript versions don't match", version);


	//
	// Compare properties
	//

	int propCount;
	stream >> propCount;
	if(propCount != asEP_STRING_ENCODING)
		throw invalid_value_exception("Number of engine properties doesn't match", propCount);

	for(int i = 0; i < propCount; ++i)
	{
		asPWORD prop;
		stream >> prop;
		if(prop != m_engine->GetEngineProperty((asEEngineProp)i))
			throw invalid_value_exception("Property doesn't match", prop);
	}


	//
	// Compare global functions
	//

	int funcCount;
	stream >> funcCount;
	if(funcCount != m_functions.size())
		throw invalid_value_exception("Number of global functions doesn't match", funcCount);
	foreach(const auto& fn, m_functions)
		fn.second.compareState(stream);


	//
	// Compare enumerations
	//

	int enumCount;
	stream >> enumCount;
	if(enumCount != m_enums.size())
		throw invalid_value_exception("Number of enumerations doesn't match", enumCount);
	foreach(const auto& en, m_enums)
		en.second.compareState(stream);


	//
	// Compare types
	//

	int typeCount;
	stream >> typeCount;
	if(typeCount != m_types.size())
		throw invalid_value_exception("Number of types doesn't match", typeCount);
	foreach(const auto& t, m_types)
		t.second.compareState(stream);


	//
	// Delimiter
	//

	auto size1 = stream.pos() - pos;
	std::streamsize size2;
	stream >> size2;
	if(size2 != size1)
		throw invalid_value_exception("Size mismatch: There's an error in saving/comparing state", size2);

	stream >> affe;
	if(affe != 0xAFFE)
		throw invalid_value_exception("Missing delimiter", affe);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::saveState(boostext::iostreams::bin_ostream& stream)
{
	// Just in case, update the cache...
	reflect();


	// Delimiter
	stream << (uint16)0xAFFE;
	auto pos = stream.pos();


	//
	// Save the version
	//

	stream << (int)ANGELSCRIPT_VERSION;


	//
	// Save properties
	//

	int propCount = asEP_STRING_ENCODING;
	stream << propCount;
	for(int i = 0; i < propCount; ++i)
	{
		stream << m_engine->GetEngineProperty((asEEngineProp)(i));
	}


	//
	// Save global functions
	//

	int funcCount = m_functions.size();
	stream << funcCount;
	foreach(const auto& fn, m_functions)
		fn.second.saveState(stream);


	//
	// Save enumerations
	//

	int enumCount = m_engine->GetEnumCount();
	stream << enumCount;
	foreach(const auto& en, m_enums)
		en.second.saveState(stream);


	//
	// Save registered types
	//

	int objCount = m_types.size();
	stream << objCount;
	foreach(const auto& type, m_types)
		type.second.saveState(stream);


	//
	// Save delimiter
	//

	stream << (stream.pos() - pos);
	stream << (uint16)0xAFFE;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::attach(Debugger::ptr debugger)
{
	// Reflection is only required when there actually is a debugger
	if(debugger)
	{
		reflect();
	}
	// If there is none, we discard all reflection data
	else
	{
		m_types.swap(TypeMap());
		m_functions.swap(FunctionMap());
		m_enums.swap(EnumMap());
	}


	m_contextPool->attach(debugger);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Engine::Script::Builder Engine::Script::System::builder() const
{
	return Builder(m_logger, m_engine);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Engine::Script::Module::ptr Engine::Script::System::moduleFromFile(QString name, const boost::filesystem::path& path)
{
	Builder builder(m_logger, m_engine);

	builder.start(name);
	builder.add(name, path);

	auto module = builder.build();
	verify(module != nullptr);
	return module;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Engine::Script::Module::ptr Engine::Script::System::moduleFromString(QString name, QString code)
{
	Builder builder(m_logger, m_engine);

	builder.start(name);
	builder.add(name, code);

	auto module = builder.build();
	verify(module != nullptr);
	return module;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Engine::Script::Module::ptr Engine::Script::System::moduleFromBytecode(QString name, const std::vector<char>& bytecode)
{
	auto data = name.toUtf8();
	auto handle = m_engine->GetModule(data.constData(), asGM_ALWAYS_CREATE);
	return Module::create(handle);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Engine::Script::ContextPool::ptr Engine::Script::System::contextPool() const
{
	return m_contextPool;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Engine::Script::System::registerGlobalProperty(const char* decl, void* pointer)
{
	int ret = m_engine->RegisterGlobalProperty(decl, pointer);
	verify(ret >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::registerInterface(const char* name)
{
	int ret = m_engine->RegisterInterface(name);
	verify(ret >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::registerInterfaceMethod(const char* name, const char* decl)
{
	int ret = m_engine->RegisterInterfaceMethod(name, decl);
	verify(ret >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::registerObjectType(const char* name, int size, asDWORD flags)
{
	int ret = m_engine->RegisterObjectType(name, size, flags);
	verify(ret >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::registerStringFactory(const char* name, const asSFuncPtr& funcPtr, asDWORD callConv)
{
	int ret = m_engine->RegisterStringFactory(name, funcPtr, callConv);
	verify(ret >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::registerObjectMethod(const char* name, const char* decl, const asSFuncPtr& funcPtr, asDWORD callConv)
{
	int ret = m_engine->RegisterObjectMethod(name, decl, funcPtr, callConv);
	verify(ret >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::registerObjectBehaviour(const char* name, asEBehaviours behaviour, const char* decl, const asSFuncPtr& funcPtr, asDWORD callConv)
{
	int ret = m_engine->RegisterObjectBehaviour(name, behaviour, decl, funcPtr, callConv);
	verify(ret >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::registerObjectProperty(const char* obj, const char* decl, int byteOffset)
{
	int ret = m_engine->RegisterObjectProperty(obj, decl, byteOffset);
	verify(ret >= 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Engine::Script::System::reflect()
{
	//
	// POD Types
	//

	addPOD(asTYPEID_VOID);
	addPOD(asTYPEID_BOOL);
	addPOD(asTYPEID_INT8);
	addPOD(asTYPEID_INT16);
	addPOD(asTYPEID_INT32);
	addPOD(asTYPEID_INT64);
	addPOD(asTYPEID_UINT8);
	addPOD(asTYPEID_UINT16);
	addPOD(asTYPEID_UINT32);
	addPOD(asTYPEID_UINT64);
	addPOD(asTYPEID_FLOAT);
	addPOD(asTYPEID_DOUBLE);


	//
	// Types
	//

	for(auto i = m_types.begin(); i != m_types.end();)
	{
		int id = i->second.id();
		auto type = pointer<asIObjectType>(m_engine->GetObjectTypeById(id));
		if(!type || i->second.isPod())
			i = m_types.erase(i);
		else
			++i;
	}


	int count = m_engine->GetObjectTypeCount();
	for(int i = 0; i < count; ++i)
	{
		auto type = pointer<asIObjectType>(m_engine->GetObjectTypeByIndex(i));
		int id = type->GetTypeId();
		m_types.insert(std::make_pair(id, Type(m_this, type)));
	}


	//
	// Functions
	//

	for(auto i = m_functions.begin(); i != m_functions.end();)
	{
		int id = i->second.id();
		auto handle = pointer<asIScriptFunction>(m_engine->GetFunctionDescriptorById(id));
		if(!handle)
			i = m_functions.erase(i);
		else
			++i;
	}

	int funcCount = m_engine->GetGlobalFunctionCount();
	for(int i = 0; i < funcCount; ++i)
	{
		int  id     = m_engine->GetGlobalFunctionIdByIndex(i);
		auto handle = pointer<asIScriptFunction>(m_engine->GetFunctionDescriptorById(id));
		m_functions.insert(std::make_pair(id, Function(m_this, handle)));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool Engine::Script::System::hasType(int id) const
{
	return m_types.find(id) != m_types.end();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

int Engine::Script::System::typeCount() const
{
	return m_types.size();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

const Engine::Script::Type& Engine::Script::System::type(int id) const
{
	auto it = m_types.find(id);
	if(it == m_types.end())
		throw invalid_value_exception(id);
	else
		return it->second;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Engine::Script::Function Engine::Script::System::function(int id) const
{
	auto it = m_functions.find(id);
	if(it == m_functions.end())
		throw invalid_value_exception(id);
	else
		return it->second;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

std::vector<Engine::Script::Type> Engine::Script::System::types() const
{
	std::vector<Type> types;
	types.reserve(m_types.size());
	foreach(const auto& pair, m_types)
		types.push_back(pair.second);
	return types;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Engine::Script::System::addPOD(asETypeIdFlags type)
{
	m_types.insert(std::make_pair(type, Type(m_this, type)));
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Script::System::callback(const asSMessageInfo *msg)
{
	logger::entry::Level level;
	switch(msg->type)
	{
	case asMSGTYPE_WARNING:     level = logger::entry::warning; break;
	case asMSGTYPE_INFORMATION: level = logger::entry::info; break;
	case asMSGTYPE_ERROR:
	default:                    level = logger::entry::error; break;
	}

	if(m_logger)
		m_logger->log_detailed(level, msg->message, MLD_MODULE, msg->section, "unknown", msg->row);
	else if(m_logCallback)
		m_logCallback(logger::entry(level, msg->message, MLD_MODULE, msg->section, "unknown", msg->row, msg->col));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
